#include "menu.h"
#include <board.h>
#include <lcd/draw.h>
#include <lcd/font.h>
#include <tsd/inarea.h>
#include <pit/pitd.h>
#include <tsd/tsd.h>

//------------------------------------------------------------------------------
// MENU THEME

#include <lcd/color.h>
#include "menu_theme.h"

//------------------------------------------------------------------------------
// CONFIGURABLES

static menu_callback_t _callback;
static menu_list_p _items;
static void * _display;

//------------------------------------------------------------------------------
// SIDE BAR

#define _MENU_SIDEBAR_X         0
#define _MENU_SIDEBAR_HEIGHT    (BOARD_LCD_HEIGHT / 3)
#define _MENU_SIDEBAR_WIDTH     _MENU_SIDEBAR_HEIGHT
#define _MENU_SIDEBAR_MARGIN    5

#define _MENU_SIDEBAR_BUTTON(button,i) \
    static inarea_rect_t _sidebar_##button = { \
            .x = _MENU_SIDEBAR_X, .y = i*_MENU_SIDEBAR_HEIGHT, \
            .width = _MENU_SIDEBAR_WIDTH, .height = _MENU_SIDEBAR_HEIGHT }

_MENU_SIDEBAR_BUTTON(scroll_up,0);
_MENU_SIDEBAR_BUTTON(scroll_down,2);
_MENU_SIDEBAR_BUTTON(back,1);

#define _MENU_SIDEBAR_SCROLL_X0 (_MENU_SIDEBAR_X + (_MENU_SIDEBAR_WIDTH / 2))
#define _MENU_SIDEBAR_SCROLL_X1 (_MENU_SIDEBAR_X + _MENU_SIDEBAR_MARGIN)
#define _MENU_SIDEBAR_SCROLL_X2 (_MENU_SIDEBAR_X + _MENU_SIDEBAR_WIDTH - _MENU_SIDEBAR_MARGIN)

#define _MENU_SIDEBAR_BACK_X_TOP    (_MENU_SIDEBAR_X + _MENU_SIDEBAR_MARGIN)
#define _MENU_SIDEBAR_BACK_X_BOTTOM (_MENU_SIDEBAR_X + _MENU_SIDEBAR_WIDTH - _MENU_SIDEBAR_MARGIN)

//------------------------------------------------------------------------------
// ITEMS LIST

#define _MENU_ITEMS_LIST_X      (_MENU_SIDEBAR_WIDTH + _MENU_SIDEBAR_X)
#define _MENU_ITEMS_LIST_WIDTH  (BOARD_LCD_WIDTH - _MENU_ITEMS_LIST_X)
#define _MENU_ITEMS_LIST_HEIGHT (BOARD_LCD_HEIGHT / MENU_NUMBER_OF_ITEMS)

static inarea_rect_t _items_list [MENU_NUMBER_OF_ITEMS] =
{
        [0 ... (MENU_NUMBER_OF_ITEMS-1)] = {
                .x = _MENU_ITEMS_LIST_X,
                .y = _MENU_ITEMS_LIST_HEIGHT,
                .width = _MENU_ITEMS_LIST_WIDTH,
                .height = _MENU_ITEMS_LIST_HEIGHT
        }
};

static int _first_item_idx;
#define _MENU_NEW_ITEMS_LIST(lst) { _items = lst; _first_item_idx = 0; }

#define _MENU_SCROLLING MENU_NUMBER_OF_ITEMS

//------------------------------------------------------------------------------
// LOCAL FUNCTIONS

static inline  void _draw_scroll_up (color_t colour) {
    int y = _sidebar_scroll_up.y + _MENU_SIDEBAR_HEIGHT - 5;
    LCDD_DrawTriangle(_display,
            _MENU_SIDEBAR_SCROLL_X0,
            _sidebar_scroll_up.y + 5,
            _MENU_SIDEBAR_SCROLL_X1,
            y,
            _MENU_SIDEBAR_SCROLL_X2,
            y,
            colour);
}

static inline void _draw_scroll_down (color_t colour) {
    int y = _sidebar_scroll_down.y + 5;
    LCDD_DrawTriangle(_display,
            _MENU_SIDEBAR_SCROLL_X0,
            _sidebar_scroll_down.y + _MENU_SIDEBAR_HEIGHT - 5,
            _MENU_SIDEBAR_SCROLL_X1,
            y,
            _MENU_SIDEBAR_SCROLL_X2,
            y,
            colour);
}

static inline void _draw_back (color_t colour) {
    LCDD_DrawTriangle(_display,
            _MENU_SIDEBAR_BACK_X_TOP,
            _sidebar_back.y + (_MENU_SIDEBAR_HEIGHT / 2),
            _MENU_SIDEBAR_BACK_X_BOTTOM,
            _sidebar_back.y + _MENU_SIDEBAR_MARGIN,
            _MENU_SIDEBAR_BACK_X_BOTTOM,
            _sidebar_back.y + _MENU_SIDEBAR_HEIGHT - _MENU_SIDEBAR_MARGIN,
            colour);
}

static menu_item_t _shown_items[MENU_NUMBER_OF_ITEMS];

static void _draw_item_with_colours (
        int slot,
        color_t foreground,
        color_t backgrond,
        color_t dir_char) {
    // TODO clean hardcoded values (i blame da font)

    inarea_rect_t * rect = _items_list + slot;
    int x = _MENU_ITEMS_LIST_X + 3;
    int y = slot * _MENU_ITEMS_LIST_HEIGHT + 8;
    menu_item_p item = _shown_items + slot;
    char * c;

    LCDD_DrawRectangle(_display,
            _MENU_ITEMS_LIST_X + 1,
            rect->y + 1,
            _MENU_ITEMS_LIST_WIDTH - 1,
            _MENU_ITEMS_LIST_HEIGHT - 2,
            backgrond);

    for (c = item->name;
            *c != 0;
            ++c, x += 12) {
        LCDD_DrawChar(_display,
                x, y, *c,
                foreground);
    }

    if (item->flags && MENU_ITEM_IS_DIR) {
        LCDD_DrawChar(_display,
                _MENU_ITEMS_LIST_X + _MENU_ITEMS_LIST_WIDTH - 3 - 10,
                y, '>', dir_char);
    }
}

static inline void _draw_item (int slot) {
    _draw_item_with_colours(
            slot,
            MENU_THEME_TEXT,
            MENU_THEME_BACKGROUND,
            MENU_THEME_DIR_CHAR);
}

static inline void _draw_selected_item (int slot) {
    _draw_item_with_colours(
            slot,
            MENU_THEME_SELECTED_TEXT,
            MENU_THEME_SELECTED_TEXT_BACKGROUND,
            MENU_THEME_SELECTED_DIR_CHAR);
}

static void _draw_list (void) {
    int slot, idx, sz;
    for (slot = 0, idx = _first_item_idx, sz = _items->size;
            (slot < MENU_NUMBER_OF_ITEMS) && (idx < sz);
            ++slot, ++idx) {
        menu_list_get(_shown_items + slot, _items, idx);
        _draw_item(slot);
    }
    for (;slot < MENU_NUMBER_OF_ITEMS; ++slot) {
        LCDD_DrawRectangle(_display,
                _MENU_ITEMS_LIST_X + 1,
                slot * _MENU_ITEMS_LIST_HEIGHT + 1,
                _MENU_ITEMS_LIST_WIDTH - 1,
                _MENU_ITEMS_LIST_HEIGHT - 2,
                MENU_THEME_BACKGROUND);
    }
}

static inline void _scroll (int scroll) {
    _first_item_idx += scroll;
    _draw_list();
}

//------------------------------------------------------------------------------
// EVENT HANDLERS

static int _is_selected;
#define _SELECT()           (_is_selected = 1)
#define _SELECTION_CHECK()  { \
    if (!_is_selected) { \
        return; \
    } \
    _is_selected = 0; \
}

#define _EVENT_NONE     3
#define _EVENT_SCROLL   1
#define _EVENT_CALLBACK 2
#define _EVENT_EXIT     0

static volatile int _event;
static volatile int _event_arg;

#define _SET_SCROLL_EVENT(arg) { \
    _event_arg = arg; \
    _event = _EVENT_SCROLL; \
}
#define _SET_CALLBACK_EVENT(arg) { \
    _event_arg = arg; \
    _event = _EVENT_CALLBACK; \
}

static void _scroll_up_pressed (inarea_rect_t * pRect) {
    if (_first_item_idx == 0) {
        return;
    }
    _SELECT();
    _draw_scroll_up(MENU_THEME_SELECTED_SCROLL);
}
static void _scroll_up_moved_out (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_scroll_up(MENU_THEME_SCROLL);
}
static void _scroll_up_released (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_scroll_up(MENU_THEME_SCROLL);
    _SET_SCROLL_EVENT(-_MENU_SCROLLING);
}

static void _scroll_down_pressed (inarea_rect_t * pRect) {
    if (_first_item_idx + _MENU_SCROLLING >= _items->size) {
        return;
    }
    _SELECT();
    _draw_scroll_down(MENU_THEME_SELECTED_SCROLL);
}
static void _scroll_down_moved_out (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_scroll_down(MENU_THEME_SCROLL);
}
static void _scroll_down_released (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_scroll_down(MENU_THEME_SCROLL);
    _SET_SCROLL_EVENT(_MENU_SCROLLING);
}

static void _back_pressed (inarea_rect_t * pRect) {
    if (_items->flags && MENU_LIST_IS_ROOT)
    {
        return;
    }
    _SELECT();
    _draw_back(MENU_THEME_SELECTED_BACK);
}
static void _back_moved_out (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_back(MENU_THEME_BACK);
}
static void _back_released (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_back(MENU_THEME_BACK);
    _SET_CALLBACK_EVENT(MENU_BACK);
}

static void _item_pressed (inarea_rect_t * pRect) {
    int slot;
    slot = pRect - _items_list;
    if (_first_item_idx + slot >= _items->size) {
        return;
    }
    _SELECT();
    _draw_selected_item(slot);
}
static void _item_moved_out (inarea_rect_t * pRect) {
    _SELECTION_CHECK();
    _draw_item(pRect - _items_list);
}
static void _item_released (inarea_rect_t * pRect){
    int slot;
    _SELECTION_CHECK();
    slot = pRect - _items_list;
    _draw_item(slot);
    _SET_CALLBACK_EVENT(_first_item_idx + slot);
}

//------------------------------------------------------------------------------
// GLOBAL FUNCTIONS

inline void menu_init (void) {
    int i;
    for (i = 0; i < MENU_NUMBER_OF_ITEMS; ++i) {
        _items_list[i].y *= i;
    }
}

inline void menu_configure_callback (menu_callback_t callback) {
    _callback = callback;
}

void menu_configure_items (menu_list_p items) {
    // if _items == 0, menu_draw hasn't been called
    if (_items) {
        // if not, the menu is being shown
        _MENU_NEW_ITEMS_LIST(items);
        _draw_list();
    } else {
        _MENU_NEW_ITEMS_LIST(items);
    }
}

void menu_draw (void*display) {
    int i;
    _display = display;

    // Draw background.
    LCDD_Fill(_display, MENU_THEME_BACKGROUND);

    // Draw scroll up button.
    _draw_scroll_up(MENU_THEME_SCROLL);

    // Draw scroll down button.
    _draw_scroll_down(MENU_THEME_SCROLL);

    // Draw back button.
    _draw_back(MENU_THEME_BACK);

    // Draw line that separates the side bar from the list.
    LCDD_DrawRectangle(_display,
            _MENU_SIDEBAR_WIDTH - 1,
            0,
            _MENU_ITEMS_LIST_X - _MENU_SIDEBAR_WIDTH + 2,
            BOARD_LCD_HEIGHT,
            MENU_THEME_LINES);

    // Draw lines that separate the items form each other:
    // - upper line,
    LCDD_DrawHorizontalLine(_display,
            _MENU_SIDEBAR_WIDTH + 1,
            0,
            _MENU_ITEMS_LIST_WIDTH - 1,
            MENU_THEME_LINES);
    // - inner lines,
    for (i = 1;
            i < MENU_NUMBER_OF_ITEMS;
            ++i) {
        LCDD_DrawRectangle(_display,
                _MENU_SIDEBAR_WIDTH + 1,
                i * _MENU_ITEMS_LIST_HEIGHT - 1,
                _MENU_ITEMS_LIST_WIDTH - 1,
                2,
                MENU_THEME_LINES);
    }
    // - bottom line.
    LCDD_DrawHorizontalLine(_display,
            _MENU_SIDEBAR_WIDTH + 1,
            MENU_NUMBER_OF_ITEMS * _MENU_ITEMS_LIST_HEIGHT - 1,
            _MENU_ITEMS_LIST_WIDTH - 1,
            MENU_THEME_LINES);

    // Side bar events:
    // - scroll up,
    TSD_InareaAdd(_scroll_up_pressed,
            _scroll_up_moved_out,
            _scroll_up_released,
            &_sidebar_scroll_up,
            1);
    // - scroll down,
    TSD_InareaAdd(_scroll_down_pressed,
            _scroll_down_moved_out,
            _scroll_down_released,
            &_sidebar_scroll_down,
            1);
    // - back.
    TSD_InareaAdd(_back_pressed,
            _back_moved_out,
            _back_released,
            &_sidebar_back,
            1);

    // List events.
    for (i = 0;
            i < MENU_NUMBER_OF_ITEMS;
            ++i) {
        TSD_InareaAdd(_item_pressed,
                _item_moved_out,
                _item_released,
                _items_list + i,
                1);
    }

    PITD_Configure(TSD_Update_PenState, 10000, 0);
    TSD_SetPitPeriod(10);
    _event = _EVENT_NONE;
    PITD_Start();

    // Draw list.
    _draw_list();

    while (_event != _EVENT_EXIT) {
        if (_event == _EVENT_SCROLL) {
            _event = _EVENT_NONE;
            _scroll(_event_arg);
        }
        if (_event == _EVENT_CALLBACK) {
            _event = _EVENT_NONE;
            _callback(_event_arg);
        }
    }
}

inline void menu_exit (void) {
    PITD_Stop();
    _event = _EVENT_EXIT;
    TSD_InareaRemoveAll();
    _items = 0;
}
